#ifndef INCLUDE_SHM_LOG_SERVICE_H
#define INCLUDE_SHM_LOG_SERVICE_H

#include <stdint.h>
#include <stdio.h>
#include "shm_log.h"

typedef struct shm_log_channel {
  shm_queue_t queue;
  FILE *log_file;
  uint32_t file_roll_size_bytes;
  uint32_t max_file_count;
  uint32_t write_bytes;
  uint32_t file_index;
  char log_file_name[SHM_LOG_MAX_FILE_NAME];
} shm_log_channel_t;

typedef struct shm_log_service {
  uint32_t          num_channels;
  shm_log_channel_t channels[SHM_LOG_MAX_CHANNEL];
  bool running;
} shm_log_service_t;

SHM_API int  shm_log_service_init(shm_log_service_t *service, const char* config);
SHM_API void shm_log_service_deinit(shm_log_service_t *service);
SHM_API void shm_log_service_run(shm_log_service_t *service);
SHM_API void shm_log_service_stop(shm_log_service_t *service);

#endif //INCLUDE_SHM_LOG_SERVICE_H

#ifdef SHM_LOG_SERVICE_IMPLEMENTATION
#include "shm_ini.h"
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

struct shm_log_service_config {
  char name[SHM_LOG_MAX_NAME];
  char shm_queue[SHM_LOG_MAX_NAME];
  char log_file[SHM_LOG_MAX_FILE_NAME];
  uint32_t elem_size;
  uint32_t elem_count;
  uint32_t max_file_size;
  uint32_t max_file_count;
};

struct shm_log_service_configs {
  char section[SHM_LOG_MAX_NAME];
  uint32_t num_configs;
  struct shm_log_service_config configs[SHM_LOG_MAX_CHANNEL];
};

static int _shm_log_service_ini_parse(void *user, const char *section, 
    const char *name, const char *value) {
  struct shm_log_service_configs *configs = (struct shm_log_service_configs *)user;
  struct shm_log_service_config *config = NULL;
  if (strcmp(configs->section, section) == 0) {
    config = &configs->configs[configs->num_configs - 1];
  } else {
    //new section
    config = &configs->configs[configs->num_configs];
    strncpy(config->name, section, SHM_LOG_MAX_NAME);
    strncpy(configs->section, section, SHM_LOG_MAX_NAME);
    config->elem_size = 1024;
    config->elem_count = 1024;
    ++configs->num_configs;
  }

  if (strcmp(name, "shm_queue") == 0) {
    strncpy(config->shm_queue, value, SHM_LOG_MAX_NAME);
  } else if (strcmp(name, "log_file") == 0) {
    strncpy(config->log_file, value, SHM_LOG_MAX_FILE_NAME);   
  } else if (strcmp(name, "elem_size") == 0) {
    config->elem_size = atoi(value);
  } else if (strcmp(name, "elem_count") == 0) {
    config->elem_count = atoi(value);
  } else if (strcmp(name, "max_file_size") == 0) {
    config->max_file_size = atoi(value);
  } else if (strcmp(name, "max_file_count") == 0) {
    config->max_file_count = atoi(value);
  }
  return 0;
}

SHM_API int shm_log_service_init(shm_log_service_t *service, const char* config) {
  struct shm_log_service_configs configs;
  shm_queue_options_t options;
  FILE *file = fopen(config, "r");
  if (!file) {
    return SHM_QUEUE_ERROR_OPEN;
  }

  service->num_channels = 0;
  memset(&configs, 0, sizeof(configs));
  ini_parse_file(file, _shm_log_service_ini_parse, &configs);
  fclose(file);

  for (uint32_t i = 0; i < configs.num_configs; ++i) {
    struct shm_log_service_config *config = &configs.configs[i];
    file = fopen(config->log_file, "a+");
    if (file) {
      shm_log_channel_t *channel = &service->channels[service->num_channels];
      
      options.elem_count = config->elem_count;
      options.elem_size = config->elem_size;
      options.mode = SHM_QUEUE_MODE_READ | SHM_QUEUE_MODE_CREATE;
      options.name = config->shm_queue;
      strncpy(channel->log_file_name, config->log_file, SHM_LOG_MAX_FILE_NAME);
      channel->log_file = file;
      channel->max_file_count = config->max_file_count;
      channel->file_roll_size_bytes = config->max_file_size * SHM_LOG_1MB;
      if (channel->file_roll_size_bytes < SHM_LOG_1MB) {
        channel->file_roll_size_bytes = SHM_LOG_1MB;
      }

      fseek(file, 0, SEEK_END);
      channel->write_bytes = ftell(file);
      shm_queue_open(&channel->queue, &options);
      ++service->num_channels;
    } else {
      printf("open file error %s\n", config->log_file);
    }
  }
  return 0;
}

SHM_API void shm_log_service_deinit(shm_log_service_t *service) {
  for (uint32_t i = 0; i < service->num_channels; ++i) {
    fclose(service->channels[i].log_file);
    shm_queue_close(&service->channels[i].queue);
  }
  service->num_channels = 0;
}

static void _get_file_name(const char* src_name, char* dst_name, uint32_t index) {
  if (index == 0) {
    strcpy(dst_name, src_name);
  } else {
    const char* find = strrchr(src_name, '.');
    if (find) {
      char buf[16];
      int len = find - src_name;
      strncpy(dst_name, src_name, len);
      dst_name[len] = '\0';
      sprintf(buf, "%u.", index);
      strncat(dst_name, buf, SHM_LOG_MAX_FILE_NAME);
      strncat(dst_name, find + 1, SHM_LOG_MAX_FILE_NAME);
    } else {
      char buf[16];
      strcpy(dst_name, src_name);
      sprintf(buf, "%u.", index);
      strncat(dst_name, buf, SHM_LOG_MAX_FILE_NAME);
    }
  }
}

static void _rotate_file(shm_log_channel_t* channel) {
  char src_file[SHM_LOG_MAX_FILE_NAME];
  char dst_file[SHM_LOG_MAX_FILE_NAME];
  struct stat stat_data;

  if (channel->log_file) {
    fflush(channel->log_file);
    fclose(channel->log_file);
    channel->log_file = NULL;
  }

  for (uint32_t i = channel->max_file_count; i > 0; --i) {
    _get_file_name(channel->log_file_name, src_file, i - 1);
    if(stat(src_file, &stat_data) != 0) {
      continue;
    }
    _get_file_name(channel->log_file_name, dst_file, i);
    remove(dst_file);
    rename(src_file, dst_file);
    printf("rename %s to %s\n", src_file, dst_file);
  }

  channel->log_file = fopen(channel->log_file_name, "a+");
  channel->write_bytes = 0;
}

SHM_API void shm_log_service_run(shm_log_service_t *service) {
  void *data;
  uint32_t data_size;
  service->running = true;


  uint32_t count = 0;
  while (service->running) {
    for (uint32_t i = 0; i < service->num_channels; ++i) {
      shm_log_channel_t* channel = &service->channels[i];
      int rslt = shm_queue_ncread(&channel->queue, &data, &data_size);
      if (rslt == SHM_QUEUE_ERROR_OK) {
        if (channel->write_bytes > channel->file_roll_size_bytes) {
          _rotate_file(channel);
        }
        channel->write_bytes += data_size + 1;
        if (channel->log_file) {
          fwrite(data, data_size, 1, channel->log_file);
          fwrite("\n", 1, 1, channel->log_file);
        }
      }
    }

    //flush
    if (++count > 1000) {
      for (uint32_t i = 0; i < service->num_channels; ++i) {
        shm_log_channel_t* channel = &service->channels[i];
        if (channel->log_file) {
          fflush(channel->log_file);
        }
      }
      count = 0;
    }
    usleep(1000);
  }
}

SHM_API void shm_log_service_stop(shm_log_service_t *service) {
  service->running = false;
}

#undef SHM_LOG_SERVICE_IMPLEMENTATION
#endif
